You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stacked on #52 (base docs/reconcile-plan-with-code). When #52 merges, GitHub retargets this PR to main.
Summary
Ask no longer runs the claude or codex CLI on the host. It runs in lane D's container, in the read-only questions phase. This closes the interim R1 exception that #52 recorded.
Before
After
Where the agent runs
Host process, temporary folder, your environment
Lane D container: read-only root, no capabilities, bounded storage
Code it can see
Only the context in the prompt
That context, plus a read-only clone of the reviewed snapshot head at /work
Commands
Turned off by CLI flags
questions phase: read, list and search only, enforced by lane D's policy
Network
Host network
Vendor-only proxy
Sign-in
Host CLI login or keychain
CLAUDE_CODE_OAUTH_TOKEN (from claude setup-token) or Codex auth.json, read from the environment and never stored
How it works
runner/question-container.ts builds the image once per worker, clones the head, allocates storage, captures the invocation (no approved argv), and starts the adapter. Storage is removed only after settled, as the D5 handoff requires.
runner/question-worker.ts: every lane D entry point is synchronous (Docker and Git calls), so a worker thread owns them and the review server stays responsive. Lane D's trust registries live in that one worker.
runner/question-agent.ts (QuestionWorker): a question settles only when the worker reports that the container and storage are gone, so a cancelled or timed-out container can't outlive the state that started it.
There is no host fallback. A missing token, a missing auth.json or no Docker fails with a message that names what's missing. Provider failures now include the vendor's short reason, for example 401 Invalid bearer token.
Behavior changes for users
Ask, including npm run demo, needs Docker plus a token or auth.json. The first question builds the agent image. If that takes longer than the two-minute question deadline, the question fails; Docker keeps the finished layers, and a retry continues from them.
A repository larger than the 512 MiB tmpfs allocation fails closed.
Tests
test/question-agent.test.ts, orchestration with injected lane D dependencies:
the invocation uses the questions phase with no argv and the reviewed head;
the token goes only to the adapter;
the input folder is read-only and contains only schema.json;
storage is released after settlement, never before;
each cancel reason maps to the right stop reason;
missing credentials fail before any Docker or Git work;
the image is built once;
storage is released when setup fails after allocation.
The worker bridge runs on a real Worker with a stub: answer passthrough, cancellation that waits for the worker's reply, and crash recovery.
test/questions.test.ts: the agent receives the configured repository and snapshot head. The timeout-then-retry regression required by AGENTS.md is already covered by "keeps cancelled invocations tracked until they settle".
test/agent-question.test.ts (real Docker; added to the Agent isolation workflow and excluded from main CI like the other Docker suites):
with a fake token, the real worker gets through image build, clone, storage, network and container start, and receives Anthropic's 401. Only a request that left the container through the vendor proxy can get that response;
the live case (CODEBOOST_RUN_AUTH_PROBES=1) asks for a random word that exists only in a file in the repository, so a correct answer shows the agent read /work.
Validation
Evidence on the merged head (after merging the updated #52 branch, which includes main at 5e97aa3):
npm run typecheck: passed.
Unit tests with CI's excludes: 464/464 passed.
npm run test:browser: 58/58 passed.
test/agent-question.test.ts on local Docker (earlier head f5b2f09): passed, with the live case skipped. CI's real-docker job also passed.
Self-review of the full diff under AGENTS.md. The live Claude case has not run; it needs a real CLAUDE_CODE_OAUTH_TOKEN.
Ownership note
The plan assigns this move to lane F. F1 (#49, merged) was docs only. F1c (#57) also edits runner/questions.ts, so whichever of #54 and #57 lands second must be rebased, fitting Ask's worker shutdown (Questions.close → QuestionWorker.close) into F1c's shutdown order. This PR edits shared files owned by the integration owner (runner/questions.ts, web/public/app.js, .github/workflows/ci.yml, .github/workflows/agent-isolation.yml), so it needs that owner's review.
Ask used to run the claude/codex CLI on the host with each CLI's own
restrictions, an interim exception to R1. It now uses lane D's invocation
boundary in the read-only "questions" phase: a clone of the reviewed
snapshot head at /work, no commands, vendor-only network, and no other
host files. There is no host fallback.
- runner/question-container.ts: build image, clone, allocate bounded
storage, capture, start the Claude/Codex adapter; release storage only
after the invocation settles. Deps are injectable for unit tests.
- runner/question-worker.ts: lane D setup is synchronous, so a worker
thread owns it and the review server stays responsive.
- runner/question-agent.ts: QuestionWorker bridge; a question settles only
when the worker reports the container and storage are gone.
- Credentials come from the environment only: CLAUDE_CODE_OAUTH_TOKEN for
Claude, CODEBOOST_CODEX_AUTH_FILE or CODEX_HOME/auth.json for Codex.
- Provider failures include the vendor's short message (e.g. a 401).
- test/agent-question.test.ts runs the path on real Docker (Agent
isolation workflow); its live case needs the auth-probe credentials.
- Plan, README, Settings copy and implementation docs updated; the R1
exception is closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Reuse the persisted answer attempt as the invocation attempt, and the
note's contextId as referencedCodeHash. Accept a result only when its
attempt and context match the captured invocation and the worker reply
carries the same attempt.
- Treat a missing exit code or any signal as a failure, not an answer.
- Keep task storage whose removal Docker did not confirm, retry removal
before the next question, and refuse Ask while any remains.
- After a worker crash, fail closed instead of starting a replacement:
its containers and storage may still exist, and reclaiming them needs
lane D's scoped recovery (#51 item 4).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If prepareFilesystems creates volumes and then fails, filesystems is never assigned because the call has not returned. The allocator's cleanup can itself fail, but this caller has no allocation handle to put in RetainedStorage, so the labeled volumes/keeper can be abandoned and the next question cannot retry them. The allocation API needs to return/retain ownership for setup failures, not only for failures after line 120.
prepareTaskFilesystems can create volumes and the keeper before a later allocation step fails; if its internal cleanup cannot confirm removal, it throws without returning a TaskFilesystems. Because filesystems is assigned only after this call returns, this path cannot add the allocation to RetainedStorage, so the remaining labeled volumes/keeper are forgotten and a later question can start. The allocator needs to return/retain an authenticated handle on setup failure or provide scoped recovery before this path is used.
Terminating the question worker dropped its only handles to storage
that Docker had not removed. Shutdown now asks the worker for one last
bounded removal, records anything still unremoved beside the review
database, and the next session refuses Ask, with the removal commands,
while any recorded container or volume still exists. The record clears
itself once they are gone; an unreadable record or unreachable daemon
keeps Ask off. Removal through D waits for its recovery handles (#51).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When task storage setup fails and lane D cannot confirm its own cleanup,
D returns no handle, so Ask cannot name the leftovers. Ask now counts
the failure, stays off for the session, records it at shutdown, and
after a restart stays off while any io.codeboost.task-storage container
or volume exists. Caller-provided allocation IDs (#51 item 3) would let
Ask name these resources instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The durable record sits beside the database path, so after a rename a
new process would not see roots recorded under the old name. Each Ask
root now carries an .owner stamp naming its lock, written under a
preparation name before the folder is renamed into place. The first
check of a process deletes unrecorded codeboost-ask-* folders whose
owner lock is free and leaves those whose owner is still running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The pre-clone Git measurement now uses the same hardening as lane D's
clone: GIT_NO_LAZY_FETCH, protocol.allow=never, no replace objects,
no hooks, no graft file, no submodule recursion.
- An .owner stamp is probed only when it names a codeboost lock file in
the temp directory; anything else counts as no owner, so a lookalike
root cannot make startup open or create a file elsewhere. Every
ledger's lock now lives there under that name.
- Regression test: startup still scans Docker after deleting a recorded
root (the reported bypass does not reproduce).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docs and one Ask error message still said io.codeboost.task-storage,
but the scan checks containers, volumes and networks labelled
io.codeboost.allocation, io.codeboost.invocation or io.codeboost.egress.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The record was written via a predictable <record>.<pid>.tmp name with
the default "w" flag, so a planted link at that name would be followed
and its target overwritten. Use a random name opened with "wx" and
delete it if the write or rename fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ails
- The server stops question admission in the same turn it starts
shutting down, so a request still arriving cannot start an agent or a
container worker during the drain. A question it saved gets a
retryable "Server stopped" answer without any agent starting; the
existing drain test now expects zero agent calls instead of one
started-then-cancelled call.
- If the final release report cannot be saved, the worker's root is no
longer deleted: it stays on disk and in the record for the next
session, and Docker leftovers remain covered by the startup scan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a failed release-report write the worker thread has stopped and
the root is already recorded, so let go of the root in this process:
it stays on disk and in the record for the next check, and close() can
release the per-review lock instead of holding it for the process.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A rejected terminate() no longer counts as a stopped thread: the Ask
root stays on disk and in the record, and the lock stays held, instead
of being removed while the worker may still be alive. Later cleanup runs
only after a termination that actually settled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #52 (base
docs/reconcile-plan-with-code). When #52 merges, GitHub retargets this PR tomain.Summary
Ask no longer runs the
claudeorcodexCLI on the host. It runs in lane D's container, in the read-onlyquestionsphase. This closes the interim R1 exception that #52 recorded./workquestionsphase: read, list and search only, enforced by lane D's policyCLAUDE_CODE_OAUTH_TOKEN(fromclaude setup-token) or Codexauth.json, read from the environment and never storedHow it works
runner/question-container.tsbuilds the image once per worker, clones the head, allocates storage, captures the invocation (no approved argv), and starts the adapter. Storage is removed only aftersettled, as the D5 handoff requires.runner/question-worker.ts: every lane D entry point is synchronous (Docker and Git calls), so a worker thread owns them and the review server stays responsive. Lane D's trust registries live in that one worker.runner/question-agent.ts(QuestionWorker): a question settles only when the worker reports that the container and storage are gone, so a cancelled or timed-out container can't outlive the state that started it.auth.jsonor no Docker fails with a message that names what's missing. Provider failures now include the vendor's short reason, for example401 Invalid bearer token.Behavior changes for users
npm run demo, needs Docker plus a token orauth.json. The first question builds the agent image. If that takes longer than the two-minute question deadline, the question fails; Docker keeps the finished layers, and a retry continues from them.Tests
test/question-agent.test.ts, orchestration with injected lane D dependencies:questionsphase with no argv and the reviewed head;schema.json;Workerwith a stub: answer passthrough, cancellation that waits for the worker's reply, and crash recovery.test/questions.test.ts: the agent receives the configured repository and snapshot head. The timeout-then-retry regression required by AGENTS.md is already covered by "keeps cancelled invocations tracked until they settle".test/agent-question.test.ts(real Docker; added to the Agent isolation workflow and excluded from main CI like the other Docker suites):401. Only a request that left the container through the vendor proxy can get that response;CODEBOOST_RUN_AUTH_PROBES=1) asks for a random word that exists only in a file in the repository, so a correct answer shows the agent read/work.Validation
Evidence on the merged head (after merging the updated #52 branch, which includes
mainat5e97aa3):npm run typecheck: passed.npm run test:browser: 58/58 passed.test/agent-question.test.tson local Docker (earlier headf5b2f09): passed, with the live case skipped. CI'sreal-dockerjob also passed.CLAUDE_CODE_OAUTH_TOKEN.Ownership note
The plan assigns this move to lane F. F1 (#49, merged) was docs only. F1c (#57) also edits
runner/questions.ts, so whichever of #54 and #57 lands second must be rebased, fitting Ask's worker shutdown (Questions.close→QuestionWorker.close) into F1c's shutdown order. This PR edits shared files owned by the integration owner (runner/questions.ts,web/public/app.js,.github/workflows/ci.yml,.github/workflows/agent-isolation.yml), so it needs that owner's review.Review-lesson audit
No review findings yet.
🤖 Generated with Claude Code